package br.com.dao;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.ejb.Stateful;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.PostPersist;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import br.com.components.DataPeriodo;
import br.com.domain.Domain;
import br.com.exception.PersistenceException;
import br.com.util.PropertiesUtil;


@Stateful
@SuppressWarnings("unchecked")
public  class GenericDAOImpl<T extends Domain, ID extends Serializable> implements  GenericDAO<T,ID>{
	
	/**************************************************************************/
	/**                                ATRIBUTOS                             **/
	/**************************************************************************/
	private Class<T> classePersistente;
	
	@PersistenceContext
    EntityManager entityManager;
	
	/**************************************************************************/
	/**                                MÉTODOS                               **/
	/**************************************************************************/
	
		
	public GenericDAOImpl() {
		Type type = getClass().getGenericSuperclass();
        this.classePersistente = (Class<T>) type;
        
        System.err.println(classePersistente.getSimpleName());
	}
    
	public T salvar(T entity) throws PersistenceException  {
       
		try {
			
			if (entity.getId() == null) {
				getEntityManager().persist(entity);
			} else {
				getEntityManager().merge(entity);
			}
			
		} catch (Exception e) {
			 System.err.println(e.getMessage());
             throw new PersistenceException(e.getMessage(), e);		
		}

		return entity;
	}
	
	public void salvar(List<T> listEntity) throws PersistenceException  {
	       
		try {
			
			for(T entity: listEntity){
				this.salvar(entity);
			}
			
		} catch (Exception e) {
			 System.err.println(e.getMessage());
             throw new PersistenceException(e.getMessage(), e);		
		}

	}
  
    public void excluir(T entity) throws PersistenceException{
    	
    	try {
			T entitySearch = (T) getEntityManager().getReference(
					entity.getClass(), entity.getId());
			if(entitySearch.getId()==null) {
				getEntityManager().remove(entity);
				getEntityManager().flush(); 
			}else{	
				T entidade = (T) getEntityManager().find(entity.getClass(), entity.getId());
				entidade = getEntityManager().merge(entidade);				
				getEntityManager().remove(entidade);
				getEntityManager().flush();  
			}
    	}
		
		catch (Exception e) {		
			if (e.getCause().toString().contains("ConstraintViolationException")) {
	            throw new PersistenceException
	                (PropertiesUtil.getMessageChave("msg-exception-genericdaoimpl-excluir"), e);		
			} 
			System.err.println(e.getMessage());
            throw new PersistenceException(e.getMessage(), e);	
		}
    }
	
    public void excluir(List<T> listEntity) throws PersistenceException{
    	for(T entity: listEntity) {
    		this.excluir(entity);
    	}
    }
    	
	public List<T> buscarTodos(String nomeClasse, String... parametros) throws PersistenceException {
		Query query;
		List<T> retorno = null;
		
		try {
			
			StringBuilder orderBy = new StringBuilder("");
			
			for (String parametro : parametros) {
				orderBy.append(parametro) ;  
			}
			
			if ("".equals(orderBy.toString().trim())){
			    query = getEntityManager().createQuery(
						"FROM " + nomeClasse );
			}else{
				query = getEntityManager().createQuery(
						"FROM " + nomeClasse + " ORDER BY " + orderBy.toString());
			}
				
			retorno = query.getResultList(); 
			
		}catch(NoResultException e){	
			return retorno;			
		}catch(Exception e){	
			System.err.println(e.getMessage());
            throw new PersistenceException(e.getMessage(), e);		
		}
		
		
		return retorno;
	}
	
	public T buscarUltimoRegistro(String nomeClasse) throws PersistenceException {
		Query query;
		T retorno = null;
		
		try {
			
			query = getEntityManager().createQuery(
				    "FROM " + nomeClasse + " ORDER BY id desc");
			
			query.setMaxResults(1);	
			List<T> list = query.getResultList(); 
			if (list != null && list.size() > 0 ) {
				retorno = list.get(0);
			}
			
		}catch(NoResultException e){	
			return retorno;			
		}catch(Exception e){	
			System.err.println(e.getMessage());
            throw new PersistenceException(e.getMessage(), e);		
		}
		return retorno;
	}
	
	public List<T> buscarTodos(String nomeClasse, int startPage, int maxPerPage, String... parametros) throws PersistenceException {
		Query query;
		List<T> retorno = null;
		
		try {
			
			StringBuilder orderBy = new StringBuilder("");
			
			for (String parametro : parametros) {
				orderBy.append(parametro);  
			}
			
			if ("".equals(orderBy.toString().trim())){
			    query = getEntityManager().createQuery(
						"FROM " + nomeClasse);
			}else{
				query = getEntityManager().createQuery(
						"FROM " + nomeClasse + " ORDER BY " + orderBy.toString() );
			}
			
			query.setFirstResult(startPage);
	        query.setMaxResults(maxPerPage);				
			retorno = query.getResultList(); 
		
			
		}catch(Exception e){	
			System.err.println(e.getMessage());
            throw new PersistenceException(e.getMessage(), e);		
		}
		
		
		return retorno;
	}
    	
    	
    	
	public Domain buscarPorId(String nomeClasse, Integer Id) throws PersistenceException {
		Class<?> classePersintente;
		Domain obj = null;
		try {
			classePersintente = Class.forName(Domain.class.getPackage()
					.getName() + "." + nomeClasse);
			obj = (Domain) getEntityManager().find(classePersintente, Id);

		}catch(NoResultException e){	
			return null;			
		}catch(Exception e){	
			System.err.println(e.getMessage());
            throw new PersistenceException(e.getMessage(), e);		
		}
		
		return obj;

	}
	
	public List<T> executarQueryList(String jpql, Object... parametros) throws PersistenceException {
		List<T> retorno = null;
		try {

			Query query = getEntityManager().createQuery(jpql);
			int i = 1;
			for (Object parametro : parametros) {
				query.setParameter(i++, parametro);
			}

			retorno = query.getResultList();
			
		}catch(NoResultException e){	
			return retorno;			
		}catch(Exception e){	
			System.err.println(e.getMessage());
            throw new PersistenceException(e.getMessage(), e);		
		}
		
		return retorno;
	}
	
    	
	public T executarQuerySingle(String jpql, Object... parametros) throws PersistenceException {
		T retorno = null;
		try{
		
			Query query = getEntityManager().createQuery(jpql);
			int i = 1;
			for (Object parametro : parametros) {
				query.setParameter(i++, parametro);
			}
			
			retorno = (T) query.getSingleResult();
		
		}catch(NoResultException e){	
			return retorno;			
		}catch(Exception e){	
			System.err.println(e.getMessage());
            throw new PersistenceException(e.getMessage(), e);		
		}
		
		return retorno;
	}
	
	
	public Boolean executarQueryExecute(String jpql, Object... parametros) throws PersistenceException {
		Boolean retorno = null;
		try{
			Query query = getEntityManager().createQuery(jpql);
			int i = 1;
			for (Object parametro : parametros) {
				query.setParameter(i++, parametro);
			}
			retorno = query.executeUpdate() > 0;
		
				
		}catch(NoResultException e){	
			return retorno;			
		}catch(Exception e){
			if (e.getCause().toString().contains("ConstraintViolationException")) {
	            throw new PersistenceException
	                (PropertiesUtil.getMessageChave("msg-exception-constraintviolation-excluir"), e);		
			} 
			System.err.println(e.getMessage());
            throw new PersistenceException(e.getMessage(), e);		
		}
		
		return retorno;
	}
	
	

	@SuppressWarnings("unused")
	private Class<?> getClassDomain (String nomeClasse) throws PersistenceException {
		Class<?> classePersintente = null;
		try {
			classePersintente = Class.forName(Domain.class.getPackage().getName() + "." + nomeClasse);
		
		} catch (Exception e) {
			System.err.println(e.getMessage());
            throw new PersistenceException(e.getMessage(), e);		
		}
		return classePersintente;
	}
	
	@SuppressWarnings({ "rawtypes" })
	public List<T> buscarPorFilter(T entity) throws PersistenceException {
	    List<T> retorno = new ArrayList<T>();
	    
	    try {
	    	
	    	
	    	String domainName     =  entity.getClass().getSimpleName();
	    	       domainName     =  domainName.substring(0, domainName.lastIndexOf("Filter"));
	    	String query          = "SELECT C FROM " + domainName + " C  WHERE ";		
		    Domain classDomain    = entity;
			Field[] fields        = classDomain.getClass().getDeclaredFields();
			boolean clauseWhere   = false;
			
			/** MONTANDO QUERY QUERY **/
			for (Field field : fields) {		
				
				/** LENDO ANOTACOES JPA **/
				Annotation[] annotations = field.getAnnotations();
				
				/** DEFININDO TIPOS DE CAMPOS **/
				String typeField    = field.getType().getName();
				String nameField    = field.getName();
				Boolean isDominio   = typeField.contains("domain");
				
				boolean adicionarField = true;
				
				/** RETIRANDO TIPO LIST, SERIAL **/
				if (typeField.contains("List")   ||
					nameField.contains("serial")) {
					continue;
				}
				
				field.setAccessible(true);
				Object value = field.get(classDomain);
				
				/** VARRENDO ANOTAÇÕES **/
				for (Annotation annotation : annotations) {
					
					/** RETIRA TRANSIENT's**/
					if(annotation instanceof Transient) {
						adicionarField = false; 
						break;
					}
				}
				
				/** RETIRA NULOS **/
				if (value == null) {
					continue;
				}
				
				/** RETIRA ID NULO OU IGUAL A ZERO **/
				if (isDominio) {
					Domain domain = (Domain) value;
					
					if (domain.getId() == null || domain.getId().equals(0)) {
						continue;
					}
				}
				
				/** RETIRA DECIMAL E INTEGER IGUAL A ZERO **/
				if (typeField.contains("BigDecimal") || typeField.contains("Integer") ) {
					
					 if (value instanceof BigDecimal) {
						 BigDecimal valor = (BigDecimal) value;
						 adicionarField   = valor.doubleValue() > 0;  
					 }
					 
					 if (value instanceof Integer) {
						 Integer valor = (Integer) value;
						 adicionarField   = valor > 0;  
					 }
					 
				} else {
					
					adicionarField = ( value != null && !"".equals(value));
				}
				
				/** RETIRA TIPO BOOLEAN **/
				if (typeField.contains("Boolean")) {	
					adicionarField = false;
				}
				
				/** RETIRA DATA PERIODO INVALIDO **/
				if (typeField.contains("DataPeriodo")) {
					DataPeriodo dataPeriodo = (DataPeriodo) value;
					adicionarField = dataPeriodo.validarObject(dataPeriodo); 
				}
				
				/** ADICIONAR CAMPO **/
				if (adicionarField) {
					clauseWhere = true;
					
					/** TIPO DOMINIO **/
					if (isDominio) {
						
						 query += " c." + nameField + ".id = :" + nameField + " and";
						
					/** NAO EH DOMINIO **/	
					} else {
						
						/** DATA PERIODO **/
						if (typeField.contains("DataPeriodo")) {
							DataPeriodo dataPeriodo = (DataPeriodo) value;
							
							query += " c." + dataPeriodo.getDataFieldFilter() + " BETWEEN  :dataInicial AND :dataFinal " ;
						} 
						/** STRING **/
						else if (typeField.contains("String")) {							
							 query += " c." + nameField + " like :" + nameField + " and";
						
						/** OUTROS **/
						} else {
							 query += " c." + nameField + " = :" + nameField + " and";
						}
					}
				}
			}
			
			/** RETIRA ULTIMO AND COM EXECAO DE PERIODOS **/
			if (query.contains("and") && !query.contains("dataInicial")) {
				 query = query.substring(0, query.lastIndexOf("and"));
			}
			
			/** BUSCAR TODOS - RETIRA WHERE **/
			if (!clauseWhere) {
				 query = query.substring(0, query.lastIndexOf("WHERE"));
			}
		   
		    Query q = getEntityManager().createQuery(query); 
		    
		    /** SETANDO PARAMETROS NA QUERY **/
		    for (Field field : fields) {		
				
				/** LENDO ANOTACOES JPA **/
				Annotation[] annotations = field.getAnnotations();
				
				/** DEFININDO TIPOS DE CAMPOS **/
				String typeField    = field.getType().getName();
				String nameField    = field.getName();
				Boolean isDominio   = typeField.contains("domain");
				
				boolean adicionarField = true;
				
				/** RETIRANDO TIPO LIST, SERIAL **/
				if (typeField.contains("List")   ||
					nameField.contains("serial")) {
					continue;
				}
				
				field.setAccessible(true);
				Object value = field.get(classDomain);
				
				/** VARRENDO ANOTAÇÕES **/
				for (Annotation annotation : annotations) {
					
					/** RETIRA TRANSIENT's**/
					if(annotation instanceof Transient) {
						adicionarField = false; 
						break;
					}
				}
				
				/** RETIRA NULOS **/
				if (value == null) {
					continue;
				}
				
				/** RETIRA ID NULO OU IGUAL A ZERO **/
				if (isDominio) {
					Domain domain = (Domain) value;
					
					if (domain.getId() == null || domain.getId() .equals(0)) {
						continue;
					}
				}
				
				/** RETIRA DECIMAL E INTEGER IGUAL A ZERO **/
				if (typeField.contains("BigDecimal") || typeField.contains("Integer") ) {	
					 
					 if (value instanceof BigDecimal) {
						 BigDecimal valor = (BigDecimal) value;
						 adicionarField   = valor.doubleValue() > 0;  
					 }
					 
					 if (value instanceof Integer) {
						 Integer valor = (Integer) value;
						 adicionarField   = valor > 0;  
					 }
					
				} else {
					
					adicionarField = ( value != null && !value.equals(""));
				}
				
				/** RETIRA TIPO BOOLEAN **/
				if (typeField.contains("Boolean")) {	
					adicionarField = false;
				}
				
				/** RETIRA DATA PERIODO INVALIDO **/
				if (typeField.contains("DataPeriodo")) {
					DataPeriodo dataPeriodo = (DataPeriodo) value;
					adicionarField = dataPeriodo.validarObject(dataPeriodo); 
				}
				
				if (adicionarField) {
					clauseWhere = true;
					
					/** TIPO DOMINIO **/
					if (isDominio) {
						
						q.setParameter(nameField, ((Domain) value).getId());			
					
					/** NAO EH DOMINIO **/	
					} else {
						
						/** DATA PERIODO **/
						if (typeField.contains("DataPeriodo")) {
							DataPeriodo dataPeriodo = (DataPeriodo) value;
							q.setParameter("dataInicial", (Date) dataPeriodo.getDataInicial() , TemporalType.TIMESTAMP);
							q.setParameter("dataFinal", (Date) dataPeriodo.getDataFinal() , TemporalType.TIMESTAMP);
						} 
						else 
						
						/** STRING **/
						if (typeField.contains("String")) {
							q.setParameter(nameField, "%" + value + "%");	
						}else
							
						/** DATE **/ 		
						if (typeField.contains("Date")) {
 							q.setParameter(nameField, (java.util.Date) value , TemporalType.DATE);
 						
 						/** OUTROS **/	
						} else {
							q.setParameter(nameField, value);	
						}
					}
				}
		    }
		   
		    List resultList = q.getResultList();
	        if (resultList != null) {
	            retorno.addAll(resultList);
	        }    
		
		} catch (Exception e) {
			System.err.println(e.getMessage());
	        throw new PersistenceException(e.getMessage(), e);		
		}
	   
	    return retorno;
	}
	
	@PostPersist
	private void postPesistence(){
		System.err.println();
	}
	
	
	
	/**************************************************************************/
	/**                           GET's E SET's                              **/
	/**************************************************************************/
	
	
	public EntityManager getEntityManager() {
		return entityManager;
	}

	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}


	public Class<T> getClassePersistente() {
		return classePersistente;
	}


	public void setClassePersistente(Class<T> classePersistente) {
		this.classePersistente = classePersistente;
	}

	

	
    

    
}
